#pragma once
//设计3.2.1原则的1超市
#include<iostream>
#include<queue>
#include<unistd.h>
#include<pthread.h>

namespace BlockQueuemode
{
    static int gcap = 10;
    template<class T>
    class BlockQueue
    {
    private:
        //容器是否满了
        bool full()
        {
            return _q.size() == _cap;
        }
        //容器是否为空
        bool Empty()
        {
            return _q.empty();
        }
    public:
        BlockQueue(int cap = gcap):_cap(cap)
        {   
            pthread_mutex_init(&_mutex,nullptr);//初始化锁
            pthread_cond_init(&_cond1,nullptr); //初始化条件变量
            pthread_cond_init(&_cond2,nullptr); //初始化条件变量
        }
        ~BlockQueue()
        {
            pthread_mutex_destroy(&_mutex);//释放锁
            //释放条件变量
            pthread_cond_destroy(&_cond1);
            pthread_cond_destroy(&_cond2);
        }
        //入队列
        void Equeue(const T& data)//生产者
        {
            pthread_mutex_lock(&_mutex);
            //你想放数据，就能放数据吗？？生产数据是有条件的！
            //结论1：在临界区中等待是必然的（目前）
            if(full())
            {
                //等待
                //在临界区等待是必然的
                //1.也就是说，等待的时候，必定是持有锁的，持有锁是有问题的
                //2.未来消费者也要申请锁，也就是说生产者一直等，消费者无法申请锁就无法消费
                //所以这个函数提供了第二个参数

                //第二个参数是，等待时会释放锁
                _1_num++;
                pthread_cond_wait(&_cond1,&_mutex);//消费者还没来得及解锁，这个就会在锁上等
                //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
                _1_num--;
            }
            //走到这里要么if条件不满足，要么线程被唤醒

            //没有满，才入队列
            _q.push(data);

            //有消费者线程等待-就唤醒
            if(_2_num)
            {
                pthread_cond_signal(&_cond2);
            }
            pthread_mutex_unlock(&_mutex);
        }
        //出队列-带出去
        void Pop(T* out)//消费者
        {
            pthread_mutex_lock(&_mutex);
            if(Empty())
            {
                _2_num++;
                pthread_cond_wait(&_cond2,&_mutex);//等待
                //当返回，线程被唤醒就会自动重新申请并持有锁(因为它会在临界区内醒来！)
                _2_num--;
            }
            //走到这里要么if条件不满足，要么线程被唤醒

           *out = _q. front();
           _q.pop();
           //有生产者线程等待-就唤醒
           if(_1_num)
           {
                pthread_cond_signal(&_cond1);
           }
           pthread_mutex_unlock(&_mutex);
        }
    private:
        std::queue<T> _q;       //保存数据的容器-临界资源
        int _cap;               //阻塞队列的最大容量
        pthread_mutex_t _mutex; //互斥-定义锁

        pthread_cond_t _cond1;  //条件变量-生产者
        pthread_cond_t _cond2;  //条件变量-消费者

        //线程等待的个数
        int _1_num;//生产者等待个数
        int _2_num;//消费者等待个数
    };
}